13장 자원끼리 서로 가리키기 (입문판)

출처: API Design Patterns (Manning, JJ Geewax) — 13장을 입문자용으로 풀어 씀.

이 장에서 딱 5가지만 기억하면 됩니다. 1. 한 자원이 다른 자원을 "이름표(식별자)"로 가리킨다 — 이게 교차 참조. 2. 참조 필드 이름은 보통 대상 + Id (예: authorId). 대상이 그때그때 바뀌면 targetId + targetType 두 개를 쓴다. 3. 가리키던 대상이 삭제되면 "허상 포인터"가 생긴다. 대응은 3가지인데, 그냥 허용하는 게 기본. 4. 식별자만 저장하면 "참조", 데이터 복사본까지 담으면 "값". 기본은 참조. 5. 참조의 단점은 데이터를 보려고 API를 두 번 부른다는 것 — 이건 GraphQL 같은 도구로 줄일 수 있다.


13.1 자원은 혼자 살지 않는다

도서관에서 책을 정리한다고 해봅시다.

책 카드에 저자 이름을 직접 적었어요. "해리포터 — 저자: J.K.롤링" 이렇게요.

그런데 저자가 어느 날 이름을 바꿉니다. "이제부터 Joanne Rowling으로 불러주세요."

이 저자가 쓴 책이 7권이면? 카드 7장을 전부 찾아서 고쳐야 합니다. 70권이면 70장. 700권이면 700장. 손이 떨리죠?

이렇게 짜면 터집니다. 그래서 똑똑한 사서는 다르게 합니다.

책 카드에는 저자 이름 대신 "저자 번호 A-123"만 적어요. 실제 이름은 저자 카드 한 장에만 적어둡니다. 저자가 이름을 바꾸면? 저자 카드 한 장만 고치면 끝.

이렇게 한 자원이 다른 자원을 "번호(식별자)"로 가리키는 것, 이게 바로 교차 참조(cross reference) 입니다.

식별자: 자원마다 붙은 세상에 하나뿐인 이름표. 주민등록번호 같은 것. (자세한 건 0장 용어집 참고)

비유 API/코드 위험·주의
책 카드에 저자 번호만 적기 authorId: "authors/1" 번호만 있으면 실제 이름은 따로 찾아야 함
저자 카드에 진짜 이름 Author { id, name } 이름이 바뀌면 이 카드 한 장만 고침
저자 번호가 가리키는 카드가 사라짐 삭제된 authorId 가리키는 곳이 비어버림 (뒤에서 다룸)

이걸 그림으로 보면 이렇습니다.

[Book 카드]                    [Author 카드]
id: "books/1234"               id: "authors/1"
title: "해리포터"              name: "J.K.롤링"
authorId: "authors/1" ───────> (여기를 가리킴)

Book의 authorId에는 저자의 진짜 이름이 아니라 저자의 식별자 "authors/1"만 들어갑니다. 이름이 궁금하면 그 식별자로 저자를 따로 찾아보면 돼요.

참조는 어디에나 있다 (예시 폭격)

자원이 혼자 존재하는 경우는 거의 없습니다. 거의 다 서로를 가리키고 있어요.

쇼핑몰을 떠올려 봅시다.

주문 → 고객      : customerId  (누가 주문했나)
주문 → 상품      : productId   (뭘 주문했나)
리뷰 → 상품      : productId   (어떤 상품 리뷰인가)
리뷰 → 작성자    : authorId    (누가 썼나)

블로그도 마찬가지예요.

게시글 → 작성자  : authorId
댓글   → 게시글  : postId
댓글   → 작성자  : authorId

빌드/배포 시스템도요.

빌드   → 저장소  : repositoryId
빌드   → 커밋    : commitId
배포   → 빌드    : buildId

전부 똑같은 모양입니다. "가리킬 대상의 식별자를 필드에 적어둔다." 이 하나만 알면 됩니다.

참조의 범위 — 가까운 데서 먼 데로

가리키는 대상이 얼마나 멀리 있느냐에 따라 3단계로 나뉩니다. 가까울수록 다루기 쉽고, 멀수록 복잡해집니다.

범위 1: 로컬 참조 (같은 API 안)
  Book → Author   같은 도서 API 안에서 서로 가리킴
  → 가장 흔하고 관리가 제일 쉬움

범위 2: 같은 회사의 다른 API
  주문 → 결제내역   주문 API가 결제 API를 가리킴
  → 같은 회사라 믿을 만하지만, 결제 API가 바뀌면 영향 받음

범위 3: 전역 참조 (바깥 서비스)
  Book → ISBN      도서 API가 국제 ISBN 데이터베이스를 가리킴
  → 표준 주소(URI)를 쓰고, 그쪽 서비스가 멈추면 확인 불가
비유 API/코드 위험·주의
같은 건물 옆방 사람 로컬 참조 (Book→Author) 제일 쉬움
같은 회사 다른 부서 다른 API 참조 (주문→결제) 그쪽 규칙 바뀌면 영향
다른 회사 사람 전역 참조 (Book→ISBN) 상대가 문 닫으면 확인 불가

정리하면 이렇습니다.

자원은 거의 항상 다른 자원과 관계를 맺습니다. 참조는 식별자(문자열) 하나로 표현합니다. 가리키는 쪽과 가리켜지는 쪽은 완전히 따로 삽니다. 범위가 넓어질수록 관리가 어려워집니다.


13.2 참조는 어떻게 동작하는가

핵심 원리는 단순합니다.

참조 필드에 상대방의 식별자(문자열) 한 개를 저장합니다. 그 식별자가 필요하면, 그걸로 상대 자원을 따로 조회합니다.

프로그래밍을 좀 해봤다면 이런 비유가 와닿을 거예요.

// 자바에서는 객체를 직접 들고 있음
class Book {
    String title;
    Author author;     // 저자 객체 자체를 품고 있음
}

API에서는 객체를 직접 품지 않습니다. 대신 "찾아갈 주소(식별자)"만 적어둡니다.

// API에서는 식별자 문자열만 들고 있음
interface Book {
    id: string;
    title: string;
    authorId: string;   // "authors/1" — 주소만 적어둠
}
// 진짜 저자 정보가 필요하면: GET /authors/1
비유 API/코드 위험·주의
친구 집에 매번 친구를 데려다 놓기 객체를 직접 품기 (자바) 무겁고 잠김
친구 집 주소만 수첩에 적기 authorId: "authors/1" 가벼움, 단 주소 보고 찾아가야 함
적어둔 주소에 친구가 이사가버림 삭제된 참조 빈집 (허상 포인터)

좋은 점과 불편한 점

식별자만 저장하는 방식은 장점이 많습니다.

유연함      식별자만 있으면 무엇이든 가리킬 수 있다
안전함      포인터(주소)만 저장하니 서로 맞물려도 잠기지 않는다
확장됨      자원 하나가 수천 개를 가리켜도 문제없다
분리됨      가리키는 쪽과 가리켜지는 쪽이 서로 독립적이다

대신 불편한 점도 있어요.

호출 추가   상대 데이터를 보려면 GET을 한 번 더 해야 한다
빈집 위험   상대가 삭제되면 가리키는 곳이 빈집이 된다 (허상 포인터)
오타 무방비  그냥 문자열이라 엉뚱한 식별자를 넣어도 즉시 안 걸린다

정리하면 이렇습니다.

참조는 문자열 식별자로 저장합니다. 참조하는 쪽과 참조받는 쪽은 서로 독립적입니다. 상대가 이동하거나 삭제될 수 있다는 걸 항상 염두에 둡니다.


13.3 참조 필드 이름 짓기 — 정적 vs 동적

참조 필드는 이름만 봐도 "무엇을 가리키는지" 알 수 있어야 합니다.

정적 참조 — 가리킬 대상이 정해져 있다

Book은 언제나 Author를 가리킵니다. 오늘도 내일도 저자를 가리키지, 갑자기 출판사를 가리키지 않아요. 이렇게 대상 타입이 고정된 걸 정적 참조라고 합니다.

이름 규칙은 간단합니다. 가리킬 대상 + Id

interface Book {
    id: string;
    authorId: string;     // 항상 Author를 가리킴
    publisherId: string;  // 항상 Publisher(출판사)를 가리킴
    title: string;
}

authorId, publisherId, categoryId... 이름만 봐도 뭘 가리키는지 바로 보이죠.

실무 예시로 주문을 봅시다. 주문 하나가 무려 4종류의 자원을 가리킵니다.

interface Order {
    id: string;                // "orders/ord-123"
    customerId: string;        // 누가 주문했나
    shippingAddressId: string; // 어디로 배송하나
    paymentMethodId: string;   // 어떻게 결제하나
    items: OrderItem[];        // 주문 항목들
}

동적 참조 — 가리킬 대상이 그때그때 바뀐다

이번엔 "변경 이력"을 만든다고 해봅시다. 이력은 저자가 바뀐 것도, 책이 생긴 것도, 출판사가 지워진 것도 다 기록해야 합니다.

여기서 authorId 하나만 두면 곤란해요. 책 변경도 기록해야 하니까요. 그렇다고 authorId, bookId, publisherId를 다 만들면? 새 타입이 생길 때마다 필드를 추가해야 합니다. 끝이 없어요.

그래서 대상 식별자 + 대상 타입, 두 개를 씁니다. 이게 동적 참조입니다.

interface ChangeLogEntry {
    id: string;
    targetId: string;     // "authors/1" 또는 "books/234" 또는...
    targetType: string;   // "Author" 또는 "Book" 또는...
    action: string;       // "created" | "updated" | "deleted"
    description: string;
}

실제로 쓰면 이렇게 됩니다.

{ targetId: "authors/1", targetType: "Author", action: "updated",
  description: "이름이 'J.K. Rowling'에서 'Joanne Rowling'으로 변경됨" }

{ targetId: "books/234", targetType: "Book", action: "created",
  description: "새 책 '해리포터와 마법사의 돌' 생성됨" }

targetType을 봐야 비로소 targetId가 무엇을 가리키는지 압니다. 감사 로그, 변경 이력, 알림처럼 온갖 타입을 다 기록해야 하는 곳에서 씁니다.

비유 API/코드 위험·주의
우편물 받는 사람 칸이 "엄마"로 고정 정적 authorId 항상 같은 종류라 단순
우편물에 "받는 분 종류 + 이름" 둘 다 적기 동적 targetType + targetId 종류 안 보면 누군지 모름
종류 칸을 안 채움 targetType 누락 어떤 자원인지 알 길 없음

정적 vs 동적 한눈에

항목 정적 참조 동적 참조
필드 수 1개 (authorId) 2개 (targetId + targetType)
대상 타입 항상 고정 상황에 따라 달라짐
쓰는 곳 대부분의 경우 감사 로그, 알림, 변경 이력
타입 짐작 필드명으로 약간 가능 targetType 봐야 함
복잡도 낮음 중간

한 걸음 더 ▸ 기본은 정적 참조입니다. 동적 참조는 편해 보이지만 "이게 무슨 타입이지?"를 매번 확인해야 해서 실수가 늘어요. 정말 여러 타입을 다 받아야 하는 로그성 자원에서만 동적을 쓰세요.


13.4 가리키던 대상이 사라지면 — 허상 포인터

이런 일이 자주 터집니다.

저자를 만들고, 그 저자를 가리키는 책을 만들었어요. 그러다 저자를 삭제했습니다. 그런데 책의 authorId는 여전히 삭제된 저자를 가리키고 있어요.

가리키는 곳에 가보니 아무도 없습니다. 빈집이에요. 이렇게 삭제된 자원을 가리키는, 더 이상 유효하지 않은 참조허상 포인터(dangling pointer) 라고 부릅니다.

허상 포인터: 이사 간 친구 주소를 그대로 적어둔 수첩. 찾아가면 빈집. (한 줄 뜻)

직접 따라가 봅시다.

1. 저자 생성
   POST /authors { name: "Michelle Obama" }
   → { id: "authors/1", name: "Michelle Obama" }

2. 그 저자를 가리키는 책 생성
   POST /books { title: "Becoming", authorId: "authors/1" }
   → { id: "books/234", authorId: "authors/1" }

3. 저자 삭제!
   DELETE /authors/1  → 204 No Content

4. 책은 그대로 저자를 가리킴
   GET /books/234
   → { id: "books/234", authorId: "authors/1" }   ← 아직 가리킴

5. 그 저자를 찾으면?
   GET /authors/1  → 404 Not Found   ← 빈집! 허상 포인터

이걸 어떻게 막을까요? 대응 방법이 3가지 있습니다. 하나씩 봅시다.

방법 1 — 삭제를 막는다 (Restrict)

"이 저자를 가리키는 책이 있으면 저자를 못 지운다."

DELETE /authors/1
→ 409 Conflict "이 저자를 참조하는 책이 있습니다"

빈집이 생길 일이 아예 없으니 깔끔해 보입니다. 하지만 함정이 있어요.

먼저, 저자를 지우려면 그 저자를 가리키는 책을 전부 먼저 정리해야 합니다. 번거롭죠.

더 큰 문제는 서로 맞물린 참조입니다. 이건 바로 다음에 따로 설명할게요.

방법 2 — 삭제하되 참조를 비운다 (Set Null)

"저자를 지우면, 그 저자를 가리키던 모든 책의 authorId를 자동으로 비운다(null)."

DELETE /authors/1
→ 저자 삭제됨
→ books/234의 authorId를 null로 바꿈
→ books/567의 authorId도 null로 바꿈
→ ... (이 저자의 모든 책을 손봄)

빈집은 안 생깁니다. 하지만 책이 수천 권이면 수천 권을 다 고쳐야 해요. 느립니다. 게다가 중간에 멈추면 일부만 고쳐진 어정쩡한 상태가 됩니다. 무엇보다, 삭제 한 번이 다른 자원까지 줄줄이 바꾸는 건 "삭제는 부수 효과가 없어야 한다"는 원칙에 어긋납니다.

부수 효과 없음: 삭제는 그 자원만 지우고, 다른 곳은 건드리지 말라는 원칙. (한 줄 뜻)

방법 3 — 삭제하고 빈집은 그냥 둔다 (허용, 권장)

"저자를 지운다. 책의 authorId는 그대로 둔다. 빈집이 생기는 건 괜찮다."

DELETE /authors/1
→ 저자 삭제됨
→ books/234의 authorId는 여전히 "authors/1" (그대로)

대신 데이터를 보는 쪽(클라이언트)이 빈집을 대비합니다.

let book = GetBook({ id: "books/234" });
try {
    let author = GetAuthor({ id: book.authorId });
    showBookWithAuthor(book, author);     // 저자 있으면 같이 표시
} catch (NotFoundError) {
    showBookWithoutAuthor(book);          // 저자 없으면 책만 표시
}

이 방법이 가장 단순하고 한결같습니다. 삭제는 자기 일만 하고, 규모가 크든 작든 똑같이 동작해요. 그래서 대부분의 경우 이 방법이 기본입니다.

비유 API/코드 위험·주의
이사 가려면 친구 수첩부터 다 지워야 함 방법 1 삭제 금지 서로 맞물리면 아무도 못 떠남
누가 이사 가면 내 수첩도 자동으로 지워줌 방법 2 참조 비우기 수첩 많으면 느리고, 삭제가 남의 일까지 함
그냥 이사 가고, 찾아가서 빈집이면 그때 대처 방법 3 허용(권장) 보는 쪽이 빈집을 대비해야 함

방법 3의 숨은 비용 — 빈집이 쌓이면 청소가 필요하다

방법 3은 단순하고 좋지만, 공짜는 아닙니다. 빈집(허상 포인터)을 "그냥 두기"로 했으니, 시간이 지나면 빈집이 한두 채씩 늘어납니다.

동네를 떠올려 봅시다. 이사 간 집을 아무도 안 치우면, 한 채는 괜찮아요. 그런데 1년, 2년 지나면 빈집이 골목마다 쌓입니다. 어느 집이 빈집인지 일일이 찾아가 봐야 알고요.

API도 똑같습니다. 저자가 삭제될 때마다 그 저자를 가리키던 책의 authorId는 빈 채로 남습니다. 한두 개는 문제없지만, 운영을 오래 하면 죽은 참조가 데이터 곳곳에 누적돼요.

비유 API/코드 위험·주의
동네에 빈집이 한 채씩 쌓임 죽은 authorId가 누적됨 한 채는 OK, 오래 쌓이면 골치
가끔 빈집을 돌며 청소함 주기적 정리(garbage collection) 안 돌면 점점 쌓임
어느 집이 비었나 순찰을 돎 죽은 참조 모니터링 따로 챙겨야 보임

그래서 빈집을 허용하는 대신, 두 가지를 같이 챙겨두면 좋습니다.

첫째, 주기적 정리입니다. 가끔 한 번씩 죽은 참조를 찾아 비워주는 청소 작업(garbage collection)을 돌립니다.

# 가끔 한 번 돌리는 청소 작업 (의사 코드)
for book in 모든_책:
    if not 저자_존재(book.author_id):   # 가리키는 저자가 빈집이면
        book.author_id = None           # 죽은 참조를 비움

둘째, 모니터링입니다. 빈집이 얼마나 쌓였는지 가끔 세어 봅니다.

죽은_참조_수 = sum(1 for b in 모든_책 if not 저자_존재(b.author_id))
경고(f"죽은 참조 {죽은_참조_수}개")   # 너무 많으면 알림

정리하면, 방법 3은 "지금 당장 편한" 선택이지만 "오래 두면 빈집이 쌓이는" 장기 비용이 있습니다. 대신 청소와 순찰(정리·모니터링)을 가끔 해주면 충분히 관리됩니다.

왜 방법 3이 권장될까 — 서로 맞물린 참조

방법 1의 진짜 약점은 순환 참조에서 드러납니다.

순환 참조: 둘이 서로를 가리키는 상태. 나는 너를, 너는 나를. (한 줄 뜻)

Book   { id: "books/1",   authorId: "authors/1" }      // 책 → 저자
Author { id: "authors/1", favoriteBookId: "books/1" }  // 저자 → 책

이 상태에서 방법 1(삭제 금지)을 쓰면 이렇게 됩니다.

Author를 지우려면 → 먼저 Book의 authorId를 지워야 함
Book을 지우려면   → 먼저 Author의 favoriteBookId를 지워야 함
→ 둘 다 상대를 먼저 지우라고 함 → 아무도 못 지움! (교착 상태)

두 사람이 문 앞에서 "너 먼저 나가" "아니 너 먼저"를 영원히 반복하는 꼴이에요. 이게 바로 방법 3(빈집 허용)이 권장되는 핵심 이유입니다. 빈집을 허용하면 이런 교착이 아예 생기지 않습니다.

허상 포인터 대응 한눈에

방법 빈집 순환 참조 큰 시스템 부수 효과 장기 비용 권장
삭제 금지 없음 교착 발생 부적합 없음 없음 주의
참조 비우기 없음 가능 느림 있음 없음 주의
빈집 허용 있음 가능 적합 없음 빈집 누적 → 정리·모니터링 필요 권장

13.5 식별자만 둘까, 복사본까지 담을까 — 값 대 참조

지금까지는 책에 authorId만 적었습니다. 저자 이름이 필요하면 따로 조회했죠. 이걸 참조 방식(pass by reference) 이라고 합니다.

let book = GetBook({ id: "books/1234" });
// { authorId: "authors/1", title: "Becoming" }

let author = GetAuthor({ id: book.authorId });   // 한 번 더 호출
// { id: "authors/1", name: "Michelle Obama" }

console.log(`${book.title} by ${author.name}`);  // "Becoming by Michelle Obama"

API를 두 번 불렀어요. 책 한 번, 저자 한 번.

매번 두 번 부르기 귀찮다면? 책 안에 저자 이름 복사본을 함께 담는 방법이 있습니다. 이걸 값 방식(pass by value) 이라고 해요.

interface BookWithInlineAuthor {
    id: string;
    title: string;
    authorId: string;     // 참조 (원본 주소)
    author: {             // 복사본 (스냅샷)
        name: string;
        // id 없음! (일부러 뺌)
    };
}

이러면 한 번만 불러도 됩니다.

let book = GetBook({ id: "books/1234" });
console.log(`${book.title} by ${book.author.name}`);  // 추가 호출 없음!

스냅샷: 그 순간을 찍은 사진. 찍은 뒤 원본이 바뀌어도 사진은 그대로. (한 줄 뜻)

비유 API/코드 위험·주의
친구 주소만 수첩에 적기 참조 authorId 가벼움, 단 찾아가야 함(호출 2번)
친구 명함을 복사해 붙여두기 author: { name } 바로 보임, 단 친구가 이름 바꾸면 명함은 옛날 것

복사본에는 왜 id를 빼야 할까

복사본에 저자 id까지 넣으면 보는 사람이 헷갈립니다.

// 잘못된 설계 — 복사본에 id가 있음
interface Book {
    id: string;
    author: { id: string; name: string };  // id가 있네?
}

// 그래서 이런 짓을 하게 됨
UpdateAuthor({ id: book.author.id, name: "New Name" });
// "복사본을 고치면 원본도 바뀌나? 아닌가?" → 대혼란

복사본은 그냥 읽기용 사진일 뿐인데, id가 있으면 "이걸로 수정해도 되나?" 하는 착각을 부릅니다.

그래서 올바른 설계는 복사본에서 id를 뺍니다.

// 올바른 설계 — 복사본에 id 없음
interface Book {
    id: string;
    authorId: string;          // 수정하려면 이걸로 (원본 주소)
    author: { name: string };  // 읽기용 사진 (id 없음)
}

수정이 필요하면 authorId로 원본에 접근하라는 뜻이 분명해집니다.

서버는 복사본을 어떻게 채울까

복사본을 쓰기로 했다면, 서버가 그 값을 채워야 합니다. 방법이 두 가지예요.

전략 1: 요청마다 합쳐서(JOIN) 최신 데이터를 가져온다
  GET /books/1234 들어오면
  → 책 테이블과 저자 테이블을 그 자리에서 합쳐 author.name을 채움
  장점: 항상 최신 이름
  단점: 매번 합치니 DB가 바빠짐

전략 2: 책 안에 저자 이름 복사본을 미리 저장해 둔다 (캐시)
  books 테이블에 author_name_cache 칸을 둠
  GET /books/1234 들어오면 → 그 칸을 바로 돌려줌 (빠름)
  단, 저자가 이름 바꾸면 → 그 저자의 모든 책 캐시도 같이 바꿔야 함
  장점: 빠름
  단점: 깜빡하면 옛날 이름이 남음 (불일치)

캐시: 자주 쓰는 값을 가까이 복사해 두는 것. 빠르지만 원본과 어긋날 수 있음. (한 줄 뜻)

참조 vs 값 한눈에

항목 참조 (식별자만) 값 (복사본)
API 호출 수 2번 1번
데이터 최신성 항상 최신 가져온 순간의 스냅샷
응답 크기 작음
서버 복잡도 낮음 높음 (JOIN/캐시 관리)
수정 혼란 없음 "복사본 고치면 원본도?" 혼란 가능
필드 이름 authorId (Id 붙음) author (Id 없음)

기본은 참조(식별자만)입니다. 복사본은 응답이 커지고 데이터가 어긋날 위험이 있으니, 꼭 필요할 때만 쓰세요.

한 걸음 더 ▸ 두 번 호출이 정말 부담이라면 GraphQL이라는 쿼리 언어로 한 번에 가져올 수도 있습니다. 자세한 건 바로 아래 트레이드오프에서 봅니다.


13.6 참조의 진짜 단점 — 호출이 늘어난다

참조 방식의 가장 큰 불편은 이것 하나입니다. 연결된 정보를 보려면 API를 여러 번 불러야 한다.

책과 저자 이름을 같이 보여주려면 이렇게 됩니다.

REST 방식: 두 번 호출
  GET /books/1234   → { authorId: "authors/1", title: "..." }
  GET /authors/1    → { name: "Michelle Obama" }

이 단점을 줄이는 도구가 있어요. GraphQL이라는 쿼리 언어는 한 번 요청으로 참조까지 펼쳐줍니다.

GraphQL 방식: 한 번 호출
  query {
    book(id: "books/1234") {
      title
      author { name }     ← 참조를 알아서 풀어줌
    }
  }
  → { title: "Becoming", author: { name: "Michelle Obama" } }

또 하나, BFF 패턴이라는 방법도 있습니다.

BFF(Backend For Frontend): 화면이 필요로 하는 여러 호출을 대신 모아주는 중간 심부름꾼. (한 줄 뜻)

비유 API/코드 위험·주의
가게마다 따로 들러 장보기 REST 참조 (호출 2번) 발품이 듦
한 번에 다 담아주는 쿠폰 GraphQL 한 번 호출 도구를 따로 갖춰야 함
심부름꾼이 대신 다 사다 줌 BFF 패턴 중간 계층을 둬야 함

핵심은, REST에서 참조의 단점(추가 호출)은 GraphQL이나 BFF로 보완할 수 있다는 것입니다.


13.7 연습문제로 정리

Q1. 복사본(값)을 저장하는 게 참조보다 유리할 때는?

거의 안 변하는 데이터인데, 매번 따로 부르는 비용이 클 때입니다. 예: 국가 코드, 통화 정보, 시간대처럼 사실상 안 바뀌는 값. 또는 인터넷이 끊겨도 보여줘야 하는 오프라인 앱.

Q2. API에서 참조 무결성을 완벽히 지키기 어려운 이유는?

여러 서버에 흩어진 시스템에서 "전부 한꺼번에" 바꾸는 게 사실상 불가능하기 때문입니다. 저자를 지울 때 그 저자를 가리키는 모든 책을 동시에 고치는 건 규모가 커지면 못 합니다. 게다가 그렇게 하면 삭제가 다른 자원까지 건드려서 "부수 효과 없음" 원칙도 깨집니다.

참조 무결성: 모든 참조가 빈집 없이 실제 대상을 제대로 가리키는 상태. (한 줄 뜻)

Q3. "참조는 항상 유효하다"던 API가 그 규칙을 포기하면 왜 위험한가?

기존 사용자들이 "빈집은 절대 없다"고 믿고 코드를 짰기 때문입니다. 어느 날 갑자기 빈집이 생기면, 그걸 대비 안 한 코드들이 줄줄이 터집니다. 약속(계약)을 바꾼 거라, 호환을 깨는 변경에 해당합니다.

Q4. 참조 필드와 복사본 필드를 어떻게 구분하나?

이름으로 구분합니다. 참조 필드는 authorId처럼 끝에 Id가 붙습니다. 복사본 필드는 author처럼 Id 없이 타입 이름만 쓰고, 안에 id도 넣지 않습니다. 이 규칙 하나로 보는 사람이 "주소인지 사진인지" 바로 압니다.


한눈에 보는 13장 용어

용어 영문 쉬운 뜻
교차 참조 Cross Reference 한 자원이 다른 자원을 식별자로 가리킴
참조 필드 Reference Field 상대 식별자를 담는 칸 (예: authorId)
허상 포인터 Dangling Pointer 삭제된 대상을 가리키는 빈집 참조
고아 레코드 Orphaned Record 가리킬 대상이 사라진 데이터
순환 참조 Circular Reference 둘이 서로를 가리키는 상태
동적 리소스 타입 Dynamic Resource Type 가리킬 타입이 그때그때 바뀌는 참조
참조 무결성 Referential Integrity 모든 참조가 멀쩡한 대상을 가리킴
인라인 복사본 Inline Copy 참조 대신 데이터를 직접 담은 사진(스냅샷)
BFF 패턴 Backend For Frontend 화면용 여러 호출을 대신 모아주는 중간 계층

다음 장 예고: 14장에서는 자원끼리의 "여러 개가 서로 얽힌" 관계를 더 또렷하게 다루는 방법을 봅니다. 지금 몰라도 됩니다.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플